#Region "Imports"
Imports System.Collections.Generic
#End Region
''' <summary>
''' Provide methods for PDF page manipulation
''' </summary>
''' <remarks></remarks>
Public Class clsPDFPage
    Inherits clsPdfDictionnary
#Region "Private members"
    Private _parentPageTree As clsPDFPageTree
    Private _currentAnnotations As New List(Of clsPdfBaseObject)    'Current annotations in the page
    Private _currentContents As New List(Of clsPdfBaseObject)       'Current contents for the page
    Private _addedAnnotations As New List(Of ClsPdfAnnotation)    'Annotations on this page
    Private _annotationsRead As Boolean 'Indicate if we have read the page's annotations
    Private _contentsRead As Boolean    'Indicate if we have read the page's content
    Private _addedImageNames As New List(Of String)        'Images names added to this page
    Private _addedStreams As New List(Of clsPdfStreamObject)          'New streams added to the page
    Private _pdf As ClsPDF  'the main pdf object
#End Region
#Region "Constructors"
    Friend Sub New(ByVal dict As Hashtable, ByVal pdf As ClsPDF)
        MyBase.New(dict)
        _annotationsRead = False
        _contentsRead = False
        _pdf = pdf
    End Sub
#End Region
#Region "Public Properties"
    ''' <summary>
    ''' Get the page number
    ''' </summary>
    ''' <value></value>
    ''' <returns>The page number</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Number As Integer
        Get
            Number = _parentPageTree.PageNumber
        End Get
    End Property
    ''' <summary>
    ''' Get or set page rotation by multiple of 90 degrees
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Rotation() As PageRotationTypes
        Get
            Dim obj As clsPdfBaseObject = _getInheritedAttribute(PDF_ROTATE)
            If Not obj Is Nothing Then
                Select Case obj.GetLong
                    Case 0
                        Rotation = PageRotationTypes.Rotation_0
                    Case 90
                        Rotation = PageRotationTypes.Rotation_90
                    Case 180
                        Rotation = PageRotationTypes.Rotation_180
                    Case 270
                        Rotation = PageRotationTypes.Rotation_270
                End Select
            Else
                'Default value
                Rotation = PageRotationTypes.Rotation_0
            End If
        End Get
        Set(ByVal value As PageRotationTypes)
            MyBase.Add(PDF_ROTATE, CreateNumericObject(value), True)
            Modified()
        End Set
    End Property
    ''' <summary>
    ''' Get page width
    ''' </summary>
    ''' <value></value>
    ''' <returns>The page's width in user units</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Width() As Double
        Get
            Dim obj As clsPdfBaseObject
            obj = _getInheritedAttribute(PDF_MEDIABOX)
            If Not obj Is Nothing Then
                'The object is an array
                Dim rect As PdfRectangle
                rect = obj.GetRectangle
                Width = rect.urx - rect.lly
            Else
                'Unknown page width
                Width = 0
            End If
        End Get
    End Property
    ''' <summary>
    ''' Get page height
    ''' </summary>
    ''' <value></value>
    ''' <returns>The page's height in user units</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Height() As Double
        Get
            Dim obj As clsPdfBaseObject
            obj = _getInheritedAttribute(PDF_MEDIABOX)
            If Not obj Is Nothing Then
                'The object is an array
                Dim rect As PdfRectangle
                rect = obj.GetRectangle
                Height = rect.ury - rect.llx
            Else
                'Unknown page width
                Height = 0
            End If
        End Get
    End Property
    ''' <summary>
    ''' The size of the user defined unit for the page.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>It is a positive number in multiples of 1/72 inch</remarks>
    Public ReadOnly Property UserUnit() As Double
        Get
            Dim obj As clsPdfBaseObject
            obj = MyBase.Item(PDF_USERUNIT)
            If Not obj Is Nothing Then
                UserUnit = obj.GetDouble
            Else
                'Default user unit
                UserUnit = 1
            End If
        End Get
    End Property
#End Region
#Region "Public Methods"
    ''' <summary>
    ''' Get all the images appearing in the page
    ''' </summary>
    ''' <returns>A list of images appearing on the page</returns>
    ''' <remarks></remarks>
    Public Function GetImages() As List(Of clsPDFImage)
        Dim namedResources, dict As clsPdfDictionnary
        Dim obj, resourceObj As clsPdfBaseObject
        Dim de As DictionaryEntry
        Dim stream As clsPdfStreamObject
        Dim image As clsPDFImage

        GetImages = New List(Of clsPDFImage)

        Dim images As New Dictionary(Of String, clsPDFImage)

        namedResources = _getNamedResource()
        If Not namedResources Is Nothing Then
            For Each de In namedResources.Hashtable
                obj = CType(de.Value, clsPdfBaseObject)
                If obj.Type = PdfObjectTypes.IndirectReferenceObject Then
                    resourceObj = _pdf.GetPdfObjectFromIndirectReferenceObject(obj)
                    If resourceObj.Type = PdfObjectTypes.IndirectObject Then
                        resourceObj.ParseChildObjects()
                        dict = resourceObj.child(0).GetPdfDictionnary
                        If dict.ContainsKey(PDF_SUBTYPE) Then
                            If dict.Item(PDF_SUBTYPE).GetString = PDF_IMAGE Then
                                stream = New clsPdfStreamObject(dict, resourceObj.child(1))
                                image = New clsPDFImage(de.Key.ToString, stream)
                                If image.IsJpeg Then
                                    If Not images.ContainsKey(image.Name) Then
                                        images.Add(image.Name, image)
                                    End If
                                End If
                            End If
                        End If
                    End If
                End If
            Next
        End If

        For Each image In images.Values
            GetImages.Add(image)
        Next
    End Function
    ''' <summary>
    ''' Erase the whole page's content, the page remains in the PDF but is blank.
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub EraseContent()
        _readContents()
        _readAnnotations()
        _currentContents.Clear()
        _currentAnnotations.Clear()
        _addedImageNames.Clear()
        _addedStreams.Clear()
        _addedAnnotations.Clear()
        MyBase.Remove(PDF_CONTENTS)
        MyBase.Remove(PDF_ANNOTS)
        Modified()
    End Sub
    ''' <summary>
    ''' Add an annotation to the page
    ''' </summary>
    ''' <param name="annotation">Annotation object</param>
    ''' <remarks>If the PDF page already has annotations, they are preserved</remarks>
    Public Sub AddAnnotation(ByVal annotation As ClsPdfAnnotation)
        _readAnnotations()
        _addedAnnotations.Add(annotation)
        Modified()
    End Sub
    ''' <summary>
    ''' Add an image to the page
    ''' </summary>
    ''' <param name="newImage">Image to place on the page</param>
    ''' <param name="lowerLeftX">Horizontal position of the lower-left corner in user unit</param>
    ''' <param name="lowerLeftY">Vertical position of the lower-left corner in user unit</param>
    ''' <param name="scaleX">Horizontal scaling in percentage, 100% = no horizontal scaling</param>
    ''' <param name="scaleY">Vertical scaling in percentage, 100% = no vertical scaling</param>
    ''' <remarks></remarks>
    Public Sub AddImage(ByRef newImage As clsPDFImage, ByVal lowerLeftX As Long, ByVal lowerLeftY As Long, ByVal scaleX As Long, ByVal scaleY As Long)
        _readContents()
        If _pdf.GetImage(newImage.Name) Is Nothing Then
            'This is a new image
            _pdf.Images.Add(newImage)
        End If
        _addedImageNames.Add(newImage.Name)

        'We create a new stream to display the image on the page
        Dim stream As New clsPdfStreamObject
        Dim scaleHeight, scaleWidth As Double
        scaleHeight = (scaleY * newImage.Height) / 100
        scaleWidth = (scaleX * newImage.Width) / 100
        Dim operators As String = String.Format(" q {1} 0 0 {2} {3} {4} cm /{0} Do Q ", newImage.Name, scaleWidth.ToString(CultureUS), scaleHeight.ToString(CultureUS), lowerLeftX, lowerLeftY)
        stream.StreamObject.AddValue(operators)
        stream.SetStreamLength()
        _addedStreams.Add(stream)

        Modified()
    End Sub
#End Region
#Region "Friend Properties"
    Friend ReadOnly Property AddedStreams() As List(Of clsPdfStreamObject)
        Get
            AddedStreams = _addedStreams
        End Get
    End Property
    Friend Property Parent() As clsPDFPageTree
        Get
            Parent = _parentPageTree
        End Get
        Set(ByVal value As clsPDFPageTree)
            _parentPageTree = value
        End Set
    End Property
    ''' <summary>
    ''' Gives acces to the annotations added to the page
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Friend ReadOnly Property AddedAnnotations() As List(Of ClsPdfAnnotation)
        Get
            AddedAnnotations = _addedAnnotations
        End Get
    End Property
    ''' <summary>
    ''' Gives access to the images added to the page
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Friend ReadOnly Property AddedImageNames() As List(Of String)
        Get
            AddedImageNames = _addedImageNames
        End Get
    End Property
#End Region
#Region "Friend Methods"
    ''' <summary>
    ''' Save the reference to the images added to page in the ressource dictionnary of the page
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub SaveImages()
        Dim image As clsPDFImage
        Dim obj As clsPdfBaseObject
        Dim resources, xobject As clsPdfDictionnary
        resources = Nothing

        obj = _getInheritedAttribute(PDF_RESOURCES)

        If Not obj Is Nothing Then
            resources = GetPdfDictionnaryFromDirectOrIndirectReference(obj, _pdf)
        End If

        If Not resources Is Nothing Then
            'We get the XObject dictionnary
            obj = resources.Item(PDF_XOBJECT)
            If obj Is Nothing Then
                'There is no XObject dictionnary, so we create one
                xobject = New clsPdfDictionnary
            Else
                'The page already containts XObjects
                xobject = GetPdfDictionnaryFromDirectOrIndirectReference(obj, _pdf)
            End If

            'We add the images names and indirect reference to the dictionnary
            Dim name As String
            For Each name In _addedImageNames
                image = _pdf.GetImage(name)
                If Not image Is Nothing Then
                    xobject.Add(name, CreateIndirectReferenceObject(image.Stream.StreamObject.ObjectNumber, image.Stream.StreamObject.GenerationNumber), True)
                End If
            Next

            'We add a a default procset
            Dim procList As New List(Of clsPdfBaseObject)
            obj = CreateNameObject(PDF_PDF)
            procList.Add(obj)
            obj = CreateNameObject(PDF_IMAGEC)
            procList.Add(obj)
            obj = CreateNameObject(PDF_TEXT)
            procList.Add(obj)
            resources.Add(PDF_PROCSET, CreateArrayObject(procList), True)

            resources.Add(PDF_XOBJECT, CreateDictionnaryObject(xobject), True)
            MyBase.Add(PDF_RESOURCES, CreateDictionnaryObject(resources), True)
        End If
    End Sub
    ''' <summary>
    ''' Save the updated contents for the page
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub SaveContents()
        Dim arrayObject As clsPdfBaseObject = CreateArrayObject(_currentContents)
        MyBase.Add(PDF_CONTENTS, arrayObject, True)
    End Sub
    ''' <summary>
    ''' Save all the current annotations in the page dictionnary
    ''' </summary>
    ''' <remarks></remarks>
    Friend Sub SaveAnnotations()
        Dim arrayObject As clsPdfBaseObject = CreateArrayObject(_currentAnnotations)
        MyBase.Add(PDF_ANNOTS, arrayObject, True)
    End Sub
    ''' <summary>
    ''' Add an indirect reference to the content in the contents array of the page
    ''' </summary>
    ''' <param name="objectNumber"></param>
    ''' <param name="generationNumber"></param>
    ''' <remarks></remarks>
    Friend Sub AddContentIndirectReference(ByVal objectNumber As Long, ByVal generationNumber As Long)
        Dim indirectReferenceObject As clsPdfBaseObject = CreateIndirectReferenceObject(objectNumber, generationNumber)
        _currentContents.Add(indirectReferenceObject)
    End Sub
    ''' <summary>
    ''' Add an indirect reference to the annotation in the annotation array of the page
    ''' </summary>
    ''' <param name="objectNumber"></param>
    ''' <param name="generationNumber"></param>
    ''' <remarks></remarks>
    Friend Sub AddAnnotationIndirectReference(ByVal objectNumber As Long, ByVal generationNumber As Long)
        Dim indirectReferenceObject As clsPdfBaseObject = CreateIndirectReferenceObject(objectNumber, generationNumber)
        _currentAnnotations.Add(indirectReferenceObject)
    End Sub
#End Region
#Region "Private Methods"
    Private Function _getNamedResource() As clsPdfDictionnary
        Dim obj, resourceObj As clsPdfBaseObject
        Dim resourceDict As clsPdfDictionnary

        _getNamedResource = Nothing
        obj = Item(PDF_RESOURCES)
        If Not obj Is Nothing Then
            Select Case obj.Type
                Case PdfObjectTypes.IndirectReferenceObject
                    resourceObj = _pdf.GetPdfObjectFromIndirectReferenceObject(obj)
                    If resourceObj.Type = PdfObjectTypes.IndirectObject Then
                        resourceObj.ParseChildObjects()
                        resourceDict = resourceObj.child(0).GetPdfDictionnary
                        obj = resourceDict.Item(PDF_XOBJECT)
                        If Not obj Is Nothing Then
                            _getNamedResource = obj.GetPdfDictionnary
                        End If
                    End If
                Case PdfObjectTypes.DictionaryObject
                    obj = obj.GetPdfDictionnary.Item(PDF_XOBJECT)
                    If Not obj Is Nothing Then
                        _getNamedResource = obj.GetPdfDictionnary
                    End If

            End Select
        End If

    End Function
    ''' <summary>
    ''' Some page attributes can be inherited from its ancestor page, this function takes car of it.
    ''' </summary>
    ''' <param name="attribute"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function _getInheritedAttribute(ByVal attribute As String) As clsPdfBaseObject
        _getInheritedAttribute = MyBase.Item(attribute)
        If _getInheritedAttribute Is Nothing Then
            'This attribute is inheritable
            _getInheritedAttribute = _parentPageTree.GetInheritedAttribute(attribute)
        End If
    End Function
    ''' <summary>
    ''' Read the page contents
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub _readContents()
        If Not _contentsRead Then
            Dim obj, indirectObj As clsPdfBaseObject
            Dim indirectReferenceList As List(Of clsPdfBaseObject)
            obj = MyBase.Item(PDF_CONTENTS)
            If Not obj Is Nothing Then
                Select Case obj.Type
                    Case PdfObjectTypes.ArrayObject
                        'Contents are stored in an array
                        indirectReferenceList = obj.GetArray
                        If Not indirectReferenceList Is Nothing Then
                            'It is not an empty content
                            For Each obj In indirectReferenceList
                                If obj.Type = PdfObjectTypes.IndirectReferenceObject Then
                                    _currentContents.Add(obj)
                                End If
                            Next
                        End If
                    Case PdfObjectTypes.IndirectReferenceObject
                        'This maybe an indirect reference to content's array
                        indirectObj = _pdf.GetPdfObjectFromIndirectReferenceObject(obj)
                        indirectObj.ParseChildObjects()
                        If indirectObj.ChildsCount = 1 And indirectObj.child(0).Type = PdfObjectTypes.ArrayObject Then
                            'This is an indirect reference to content's array
                            indirectReferenceList = indirectObj.child(0).GetArray
                            For Each obj In indirectReferenceList
                                If obj.Type = PdfObjectTypes.IndirectReferenceObject Then
                                    _currentContents.Add(obj)
                                End If
                            Next
                        Else
                            'It is a content stream
                            _currentContents.Add(obj)
                        End If
                End Select
            End If
            _contentsRead = True
        End If

    End Sub
    ''' <summary>
    ''' We read the annotations in the page
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub _readAnnotations()
        If Not _annotationsRead Then
            'We read annotations only once and only when we need them
            Dim obj As clsPdfBaseObject
            Dim indirectReferenceList As List(Of clsPdfBaseObject)
            obj = MyBase.Item(PDF_ANNOTS)
            If Not obj Is Nothing Then
                indirectReferenceList = GetPdfArrayFromDirectOrIndirectReference(obj, _pdf)
                If Not indirectReferenceList Is Nothing Then
                    For Each obj In indirectReferenceList
                        If obj.Type = PdfObjectTypes.IndirectReferenceObject Then
                            _currentAnnotations.Add(obj)
                        End If
                    Next
                End If
            End If
            _annotationsRead = True
        End If
    End Sub
#End Region
End Class
